<?php
/**
*	@author SignpostMarv Martin
*	@package UHU
*	@subpackage Sessions
*/

/**
*	Exception class used by uhu_session
*	@package Sessions
*	@subpackage UHU_exception
*	@uses uhu_exception
*/
class uhu_session_Exception extends uhu_exception
{
/**
*	@param int|null $code taken from $this->getCode()
*	@return string an extended error message
*	@uses uhu_exception::getExtendedMessage()
*/
	protected function getExtendedMessage($code=null)
	{
		$message = '%s';
		switch($code)
		{
			case 100: # prep fail
			case 101: # exec fail
				$message = 'Cannot start session: %s';
			break;

			case 102: # exec failure
			case 103: # prep failure
			case 104: # prep failure
			break;

			case 110: # PDO Read
			case 111: # PDO Write
			break;
		}
		return $message;
	}
}

/**
*	Basic class for storing session data in a PDO-supported database. Server-specific commands are ommitted from the table creation query, an extended class' constructor may be used to change the storage engine to InnoDB on MySQL, for example.
*	@package Sessions
*/
abstract class uhu_session
{
/**
*	string The SQL query used to create the session table.
*/
	const sql_create_session_table =
'CREATE TABLE IF NOT EXISTS session (
	id char(32),
	modified int,
	lifetime int,
	data text,
	PRIMARY KEY  (id)
)';

/**
*	@var PDO The PDO connection used for read+write transactions
*/
	protected $_PDO_write;

/**
*	@param string the host name
*	@param string the username
*	@param string the password
*	@param string the database name
*	@uses uhu_session::$_PDO_write
*	@uses uhu_session::setup_table()
*/
	public function __construct($type,$host,$username,$password,$dbname)
	{
		try
		{
			$this->_PDO_write = new PDO($type . ':host=' . $host . ';dbname=' . $dbname,$username,$password);
		}
		catch(PDOException $e)
		{
			throw new uhu_session_Exception('Could not establish database connection.',100);
		}
		$this->setup_table();
		try
		{
			require_once('Zend/Db.php');
			require_once('Zend/Db/Table/Abstract.php');
			require_once('Zend/Session.php');
			require_once('Zend/Session/SaveHandler/DbTable.php');
		}
		catch(Exception $e)
		{
			throw new uhu_session_Exception('Could not load required Zend modules.',101);
		}
		try
		{
			$db = Zend_Db::factory('Pdo_' . strtoupper($type[0]) . substr($type,1),array(
				'host'     => $host,
				'username' => $username,
				'password' => $password,
				'dbname'   => $dbname
			));
		}
		catch(Exception $e)
		{
			throw new uhu_session_Exception('Could not establish Zend database connection.',102);
		}
		try
		{
			Zend_Db_Table_Abstract::setDefaultAdapter($db);
		}
		catch(Exception $e)
		{
			throw new uhu_session_Exception('Could not set adapter.',103);
		}
		$config = array(
			'name'           => 'session',
			'primary'        => 'id',
			'modifiedColumn' =>'modified',
			'dataColumn'     =>'data',
			'lifetimeColumn' =>'lifetime',
		);
		try
		{
			Zend_Session::setSaveHandler(new Zend_Session_SaveHandler_DbTable($config));
		}
		catch(Exception $e)
		{
			throw new uhu_session_Exception('Could not set session handler.',104);
		}
		$this->start();
	}
/**
*	Used as a shortcut for avoiding the duplication of code that throws an exception when PDOStatement execution fails.
*	@param PDOStatement $sth The PDOStatement to be executed
*	@param int $code The error code to use when throwing uhu_session_Exception because the PDOStatement could not be executed
*	@uses uhu_session_Exception
*	@return bool true if the PDOStatement executed succesfully, false otherwise (though the uhu_session_Exception should be thrown before the code can return false
*/
	protected function throw_on_exec_fail(PDOStatement $sth,$code)
	{
		if(is_integer($code) === false)
		{
			throw new uhu_session_Exception('failure code must be integer.',300);
		}
		else
		{
			try
			{
				$exec = $sth->execute();
				$exec_fail = $exec ? false : true;
			}
			catch(PDOException $e)
			{
				$exec_fail = true;
			}
			if($exec_fail)
			{
				$errorInfo = $sth->errorInfo();
				$error_code = isset($errorInfo[1]) ? $errorInfo[1] : $errorInfo[0];
				if($error_code !== '00000')
				{
					throw new uhu_session_Exception('Could not execute query (' . $error_code . ')' . (isset($e) ? (string)$e : ''),$code);
				}
			}
		}
		return $exec;
	}

/**
*	Used as a shortcut for avoiding the duplication of code that throws an exception when SQL preparation fails.
*	@param PDO $PDO The PDO connection to prepare the query with
*	@param string $query The SQL query to prepare
*	@param int $code The error code to use when throwing uhu_session_Exception because the PDOStatement could not be executed
*	@uses uhu_session_Exception
*	@return bool true if the SQL query was prepared successfully, false otherwise (though the uhu_session_Exception should be thrown before the code can return false
*/
	protected function throw_on_prep_fail(PDO $PDO,$query,$code)
	{
		if(is_integer($code) === false)
		{
			throw new uhu_session_Exception('failure code must be integer.',400);
		}
		else if(is_string($query) === false)
		{
			throw new uhu_session_Exception('SQL query must be string.',401);
		}
		else
		{
			try
			{
				return $PDO->prepare($query);
			}
			catch(PDOException $e)
			{
				throw new uhu_session_Exception('Could not prepare SQL query.',$code);
			}
		}
	}

/**
*	Used to setup the session table on the database specified in the PDO connection
*	@uses uhu_session::throw_on_prep_fail()
*	@uses uhu_session::$_PDO_write
*	@uses uhu_session::sql_create_session_table
*	@uses uhu_session::throw_on_exec_fail
*/
	protected function setup_table()
	{
		static $sth;
		$sth = isset($sth) ? $sth : $this->throw_on_prep_fail($this->_PDO_write,self::sql_create_session_table,200);
		$this->throw_on_exec_fail($sth,201);
	}
	public function token()
	{
		return sha1(
			sha1(
				isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''
			) .
			sha1(
				isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : ''
			) .
			$this->salt()
		);
	}
	public function start($cookie_test=false)
	{
		try
		{
			Zend_Session::start();
		}
		catch(Exception $e)
		{
			throw new uhu_session_Exception('Could not start session.',500);
		}
		if($cookie_test)
		{
			if(empty($_COOKIE) || isset($_COOKIE[session_name()]) === false)
			{
				throw new uhu_session_Exception('Cookies not created.',500);
			}
		}
		$_SESSION[get_class($this) . '-token'] = isset($_SESSION[get_class($this) . '-token']) ? $_SESSION[get_class($this) . '-token'] : $this->token();
		if($_SESSION[get_class($this) . '-token'] !== $this->token())
		{
			throw new uhu_session_Exception('Session token does not match.',501);
		}
	}
	abstract protected function salt();
}
?>